/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * This maintains structures for page faults that are legal.
 * This provides information about where the data is to be picked from when
 * there is a page fault. To create a legal fault region, only the virtual
 * address space is created and it is not mapped to any physical address.
 * When a page fault occurs, the address is looked up, to find out if it is
 * a legal page fault address, a physical address is mapped in for the fault,
 * and the data required is copied over based on the descriptors in the lfault
 * subsystem. The process then resumes. Demand paging basically.
 */

#include <klib/lib.h>
#include <kernel/vfs.h>
#include <kernel/kmalloc.h>
#include <kernel/list.h>
#include <kernel/console.h>
#include <kernel/lfault.h>
#include <arch/power.h>
#include <kernel/page.h>
#include <kernel/process.h>
#include <klib/memory.h>
#include <kernel/errors.h>
#include <kernel/sched.h>


/*
 * Add a legal fault on a given process.
 * This process will then become the 'current_process'. The page fault handler
 * then can traverse the registered legal faults and determine from where to
 * load the data.
 */
void register_legal_fault(struct process *process, const char *name,
		u32 flags, u32 check_mask, void *virt,
		uint_t page_count, struct vfs_node *vfs_node,
		uint_t offset)
{
	debug("Process %s: lfault: 0x%x %u pages\n", process->name, virt, page_count);
	struct legal_fault *n = (struct legal_fault *)kmalloc(
			sizeof(struct legal_fault), "lfault");
	n->name = name;
	n->flags = flags;
	n->check_mask = check_mask;
	n->virt = virt;
	n->page_count = page_count;
	n->vfs_node = vfs_node;
	n->offset = offset;
	n->has_source = 1;
	list_attach(process->legal_faults, process->legal_faults_tail, n);
}

void register_legal_fault_no_source(struct process *process, const char *name,
		u32 flags, u32 check_mask, void *virt,
		uint_t page_count)
{
	debug("Process %s: lfault: 0x%x %u pages\n", process->name, virt, page_count);
	struct legal_fault *n = (struct legal_fault *)kmalloc(
			sizeof(struct legal_fault), "lfault");
	n->name = name;
	n->flags = flags;
	n->check_mask = check_mask;
	n->virt = virt;
	n->page_count = page_count;
	n->has_source = 0;
	list_attach(process->legal_faults, process->legal_faults_tail, n);
}

/*
 * Destroy all legal faults registered in a process
 */
void unregister_legal_faults(struct process *process)
{
	struct legal_fault *lf;
	for(lf = process->legal_faults; lf; ) {
		struct legal_fault *next_lf = lf->next;
		kfree(lf, sizeof(struct legal_fault), "lfault");
		lf = next_lf;
	}
}

/*
 * TODO We are making a read/write page. This needs to be more specific
 * based on if it's code or data. But at the same time, it might be more
 * generalized to treat code and data to be same.
 */

void handle_region(struct legal_fault *lf, void *fa_page,
		u32 fault_flags)
{
	debug("fa_page = 0x%x\n", fa_page);
//	debug("handling region for fs %s at offset %u\n", lf->vfs_node->name,
//			lf->offset);
	vm_make_phys_for_virt(current_process->vmmap, fa_page, 1,
			PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
	void *phys = virt_to_phys(current_process->vmmap, fa_page);
	if(lf->has_source) {
		//Add the page offset after the legal fault virtual address
		//to the vfs_node offset
		uint_t source_offset = lf->offset + (fa_page - lf->virt);
		//Copy over the data if there is any
		debug("read source %s at %u\n", lf->vfs_node->name, source_offset);
		vfs_read(lf->vfs_node, "data", fa_page, source_offset, PAGE_SIZE);
	} else {
		//Zero the page
		memset(fa_page, 0, PAGE_SIZE);
	}
	debug("Made phys!\n");
}

static char *fault_types[] = {"Absent", "Violation", "Read", "Write", "Kernel",
		"User", "Unexpected", "Code", "Data"};

static void flag_info(u32 flags)
{
	uint_t i;
	for(i = 0; i < 9; i++) {
		if(flags & (1 << i)) {
			debug("$B%s ", fault_types[i]);
		}
	}
	debug("$B\n");
}


/*
 * Called by the architectural page fault handler
 */
int handle_page_fault(void *fault_address, u32 fault_flags)
{
	debug("Check Legal Page fault: 0x%x, flags = ", fault_address);
	flag_info(fault_flags);
	//Verify that there is a process running and we are not in the
	//initial kernel address space
	if(current_process == null)
		return ESTINV;
	//Get the page start address where the page fault occurred
	void *fa_page = (void *)((ptr_t)fault_address
			& PAGE_ADDRESS_MASK);
	struct legal_fault *lf;
	debug("Scanning legal faults of process %s\n", current_process->name);
	list_for(current_process->legal_faults, lf) {
		void *start = lf->virt;
		void *end = lf->virt + PAGE_SIZE * lf->page_count;

		//Find out if the fault address lies in the above start & end
		if(fa_page >= start && fa_page < end) {
			debug("Found registered fault region: %s\n", lf->name);
			debug("Expected flags = ");
			flag_info(lf->flags);
			debug("Check mask = ");
			flag_info(lf->check_mask);
			debug("Anded = ");
			flag_info(fault_flags & lf->check_mask);
			if((fault_flags & lf->check_mask) == lf->flags)  {
				handle_region(lf, fa_page,
						fault_flags & lf->check_mask);
				return 0;
			}
		}
	}
	console_printf("Could not resolve fault address: 0x%x\n", fault_address);
	return ESTINV;
}
